{
 "metadata": {
  "signature": "sha256:42da1643b7a565667c9798296135f6f3e596ecd72721fd193a4e88fd06ea067d"
 },
 "nbformat": 3,
 "nbformat_minor": 0,
 "worksheets": [
  {
   "cells": [
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "LAS reader\n",
      "======================================================================\n",
      "\n",
      "This cookbook example contains a module that implements a reader for a\n",
      "LAS (Log ASCII Standard) well log file (LAS 2.0). See the [Canadian Well\n",
      "Logging Society page](http://www.cwls.org/las_info.php) about this\n",
      "format for more information."
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      "#!python\n",
      "\"\"\"LAS File Reader\n",
      "\n",
      "The main class defined here is LASReader, a class that reads a LAS file\n",
      "and makes the data available as a Python object.\n",
      "\"\"\"\n",
      "\n",
      "# Copyright (c) 2011, Warren Weckesser\n",
      "#\n",
      "# Permission to use, copy, modify, and/or distribute this software for any\n",
      "# purpose with or without fee is hereby granted, provided that the above\n",
      "# copyright notice and this permission notice appear in all copies.\n",
      "#\n",
      "# THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n",
      "# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n",
      "# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n",
      "# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n",
      "# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n",
      "# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n",
      "# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n",
      "\n",
      "\n",
      "import re\n",
      "import keyword\n",
      "\n",
      "import numpy as np\n",
      "\n",
      "\n",
      "def isidentifier(s):\n",
      "    if s in keyword.kwlist:\n",
      "        return False\n",
      "    return re.match(r'^[a-z_][a-z0-9_]*$', s, re.I) is not None\n",
      "\n",
      "\n",
      "def _convert_to_value(s):\n",
      "    try:\n",
      "        value = int(s)\n",
      "    except ValueError:\n",
      "        try:\n",
      "            value = float(s)\n",
      "        except ValueError:\n",
      "            value = s\n",
      "    return value\n",
      "\n",
      "\n",
      "class LASError(Exception):\n",
      "    pass\n",
      "\n",
      "\n",
      "class LASItem(object):\n",
      "    \"\"\"This class is just a namespace, holding the attributes 'name',\n",
      "    'units', 'data', 'value', and 'descr'.  'value' is the numerical\n",
      "    value of 'data', if it has a numerical value (specifically, if\n",
      "    int() or float() don't raise an exception when given the value\n",
      "    of the 'data' attribute).\n",
      "\n",
      "    A class method, from_line(cls, line), is provided to parse\n",
      "    a line from a LAS file and create a LASItem instance.\n",
      "    \"\"\"\n",
      "    def __init__(self, name, units='', data='', descr=''):\n",
      "        self.name = name\n",
      "        self.units = units\n",
      "        self.data = data\n",
      "        self.value = _convert_to_value(data)\n",
      "        self.descr = descr\n",
      "\n",
      "    def __str__(self):\n",
      "        s = (\"name='%s', units='%s', data='%s', descr='%s'\" %\n",
      "                (self.name, self.units, self.data, self.descr))\n",
      "        return s\n",
      "\n",
      "    def __repr__(self):\n",
      "        s = str(self)\n",
      "        return \"LASItem(%s)\" % s\n",
      "\n",
      "    @classmethod\n",
      "    def from_line(cls, line):\n",
      "        first, descr = line.rsplit(':', 1)\n",
      "        descr = descr.strip()\n",
      "        name, mid = first.split('.', 1)\n",
      "        name = name.strip()\n",
      "        if mid.startswith(' '):\n",
      "            # No units\n",
      "            units = ''\n",
      "            data = mid\n",
      "        else:\n",
      "            units_data = mid.split(None, 1)\n",
      "            if len(units_data) == 1:\n",
      "                units = units_data[0]\n",
      "                data = ''\n",
      "            else:\n",
      "                units, data = units_data\n",
      "        return LASItem(name=name, units=units, data=data.strip(),\n",
      "                       descr=descr.strip())\n",
      "\n",
      "\n",
      "def _read_wrapped_row(f, n):\n",
      "    \"\"\"Read a \"row\" of data from the Ascii section of a \"wrapped\" LAS file.\n",
      "\n",
      "    `f` must be a file object opened for reading.\n",
      "    `n` is the number of fields in the row.\n",
      "\n",
      "    Returns the list of floats read from the file.\n",
      "    \"\"\"\n",
      "    depth = float(f.readline().strip())\n",
      "    values = [depth]\n",
      "    while len(values) < n:\n",
      "        new_values = [float(s) for s in f.readline().split()]\n",
      "        values.extend(new_values)\n",
      "    return values\n",
      "\n",
      "\n",
      "def _read_wrapped_data(f, dt):\n",
      "    data = []\n",
      "    ncols = len(dt.names)\n",
      "    while True:\n",
      "        try:\n",
      "            row = _read_wrapped_row(f, ncols)\n",
      "        except Exception:\n",
      "            break\n",
      "        data.append(tuple(row))\n",
      "    data = np.array(data, dtype=dt)\n",
      "    return data\n",
      "\n",
      "\n",
      "class LASSection(object):\n",
      "    \"\"\"Represents a \"section\" of a LAS file.\n",
      "\n",
      "    A section is basically a collection of items, where each item has the\n",
      "    attributes 'name', 'units', 'data' and 'descr'.\n",
      "\n",
      "    Any item in the section whose name is a valid Python identifier is\n",
      "    also attached to the object as an attribute.  For example, if `s` is a\n",
      "    LASSection instance, and the corresponding section in the LAS file\n",
      "    contained this line:\n",
      "\n",
      "    FD   .K/M3               999.9999        :  Fluid Density\n",
      "\n",
      "    then the item may be referred to as `s.FD` (in addition to the longer\n",
      "    `s.items['FD']`).\n",
      "\n",
      "    Attributes\n",
      "    ----------\n",
      "    items : dict\n",
      "        The keys are the item names, and the values are LASItem instances.\n",
      "    names : list\n",
      "        List of item names, in the order they were read from the LAS file.\n",
      "\n",
      "    \"\"\"\n",
      "    def __init__(self):\n",
      "        # Note: In Python 2.7, 'items' could be an OrderedDict, and\n",
      "        # then 'names' would not be necessary--one could use items.keys().\n",
      "        self.items = dict()\n",
      "        self.names = []\n",
      "\n",
      "    def add_item(self, item):\n",
      "        self.items[item.name] = item\n",
      "        self.names.append(item.name)\n",
      "        if isidentifier(item.name) and not hasattr(self, item.name):\n",
      "            setattr(self, item.name, item)\n",
      "\n",
      "    def display(self):\n",
      "        for name in self.names:\n",
      "            item = self.items[name]\n",
      "            namestr = name\n",
      "            if item.units != '':\n",
      "                namestr = namestr + (\" (%s)\" % item.units)\n",
      "            print \"%-16s %-30s [%s]\" % (namestr, \"'\" + item.data + \"'\",\n",
      "                                        item.descr)\n",
      "\n",
      "\n",
      "class LASReader(object):\n",
      "    \"\"\"The LASReader class holds data from a LAS file.\n",
      "\n",
      "    This reader only handles LAS 2.0 files (as far as I know).\n",
      "\n",
      "    Constructor\n",
      "    -----------\n",
      "    LASReader(f, null_subs=None)\n",
      "\n",
      "    f : file object or string\n",
      "        If f is a file object, it must be opened for reading.\n",
      "        If f is a string, it must be the filename of a LAS file.\n",
      "        In that case, the file will be opened and read.\n",
      "\n",
      "    Attributes for LAS Sections\n",
      "    ---------------------------\n",
      "    version : LASSection instance\n",
      "        This LASSection holds the items from the '~V' section.\n",
      "\n",
      "    well : LASSection instance\n",
      "        This LASSection holds the items from the '~W' section.\n",
      "\n",
      "    curves : LASection instance\n",
      "        This LASSection holds the items from the '~C' section.\n",
      "\n",
      "    parameters : LASSection instance\n",
      "        This LASSection holds the items from the '~P' section.\n",
      "\n",
      "    other : str\n",
      "        Holds the contents of the '~O' section as a single string.\n",
      "\n",
      "    data : numpy 1D structured array\n",
      "        The numerical data from the '~A' section.  The data type\n",
      "        of the array is constructed from the items in the '~C'\n",
      "        section.\n",
      "\n",
      "    Other attributes\n",
      "    ----------------\n",
      "    data2d : numpy 2D array of floats\n",
      "        The numerical data from the '~A' section, as a 2D array.\n",
      "        This is a view of the same data as in the `data` attribute.\n",
      "\n",
      "    wrap : bool\n",
      "        True if the LAS file was wrapped. (More specifically, this\n",
      "        attribute is True if the data field of the item with the\n",
      "        name 'WRAP' in the '~V' section has the value 'YES'.)\n",
      "\n",
      "    vers : str\n",
      "        The LAS version. (More specifically, the value of the data\n",
      "        field of the item with the name 'VERS' in the '~V' section).\n",
      "\n",
      "    null : float or None\n",
      "        The numerical value of the 'NULL' item in the '~W' section.\n",
      "        The value will be None if the 'NULL' item was missing.\n",
      "\n",
      "    null_subs : float or None\n",
      "        The value given in the constructor, to be used as the\n",
      "        replacement value of each occurrence of `null_value` in\n",
      "        the log data.  The value will be None (and no substitution\n",
      "        will be done) if the `null_subs` argument is not given to\n",
      "        the constructor.\n",
      "\n",
      "    start : float, or None\n",
      "        Numerical value of the 'STRT' item from the '~W' section.\n",
      "        The value will be None if 'STRT' was not given in the file.\n",
      "\n",
      "    start_units : str\n",
      "        Units of the 'STRT' item from the '~W' section.\n",
      "        The value will be None if 'STRT' was not given in the file.\n",
      "\n",
      "    stop : float\n",
      "        Numerical value of the 'STOP' item from the '~W' section.\n",
      "        The value will be None if 'STOP' was not given in the file.\n",
      "\n",
      "    stop_units : str\n",
      "        Units of the 'STOP' item from the '~W' section.\n",
      "        The value will be None if 'STOP' was not given in the file.\n",
      "\n",
      "    step : float\n",
      "        Numerical value of the 'STEP' item from the '~W' section.\n",
      "        The value will be None if 'STEP' was not given in the file.\n",
      "\n",
      "    step_units : str\n",
      "        Units of the 'STEP' item from the '~W' section.\n",
      "        The value will be None if 'STEP' was not given in the file.\n",
      "\n",
      "    \"\"\"\n",
      "\n",
      "    def __init__(self, f, null_subs=None):\n",
      "        \"\"\"f can be a filename (str) or a file object.\n",
      "\n",
      "        If 'null_subs' is not None, its value replaces any values in the data\n",
      "        that matches the NULL value specified in the Version section of the LAS\n",
      "        file.\n",
      "        \"\"\"\n",
      "        self.null = None\n",
      "        self.null_subs = null_subs\n",
      "        self.start = None\n",
      "        self.start_units = None\n",
      "        self.stop = None\n",
      "        self.stop_units = None\n",
      "        self.step = None\n",
      "        self.step_units = None\n",
      "\n",
      "        self.version = LASSection()\n",
      "        self.well = LASSection()\n",
      "        self.curves = LASSection()\n",
      "        self.parameters = LASSection()\n",
      "        self.other = ''\n",
      "        self.data = None\n",
      "\n",
      "        self._read_las(f)\n",
      "\n",
      "        self.data2d = self.data.view(float).reshape(-1, len(self.curves.items))\n",
      "        if null_subs is not None:\n",
      "            self.data2d[self.data2d == self.null] = null_subs\n",
      "\n",
      "    def _read_las(self, f):\n",
      "        \"\"\"Read a LAS file.\n",
      "\n",
      "        Returns a dictionary with keys 'V', 'W', 'C', 'P', 'O' and 'A',\n",
      "        corresponding to the sections of a LAS file.  The values associated\n",
      "        with keys 'V', 'W', 'C' and 'P' will be lists of Item instances.  The\n",
      "        value associated with the 'O' key is a list of strings.  The value\n",
      "        associated with the 'A' key is a numpy structured array containing the\n",
      "        log data.  The field names of the array are the mnemonics from the\n",
      "        Curve section of the file.\n",
      "        \"\"\"\n",
      "        opened_here = False\n",
      "        if isinstance(f, basestring):\n",
      "            opened_here = True\n",
      "            f = open(f, 'r')\n",
      "\n",
      "        self.wrap = False\n",
      "\n",
      "        line = f.readline()\n",
      "        current_section = None\n",
      "        current_section_label = ''\n",
      "        while not line.startswith('~A'):\n",
      "            if not line.startswith('#'):\n",
      "                if line.startswith('~'):\n",
      "                    if len(line) < 2:\n",
      "                        raise LASError(\"Missing section character after '~'.\")\n",
      "                    current_section_label = line[1:2]\n",
      "                    other = False\n",
      "                    if current_section_label == 'V':\n",
      "                        current_section = self.version\n",
      "                    elif current_section_label == 'W':\n",
      "                        current_section = self.well\n",
      "                    elif current_section_label == 'C':\n",
      "                        current_section = self.curves\n",
      "                    elif current_section_label == 'P':\n",
      "                        current_section = self.parameters\n",
      "                    elif current_section_label == 'O':\n",
      "                        current_section = self.other\n",
      "                        other = True\n",
      "                    else:\n",
      "                        raise LASError(\"Unknown section '%s'\" % line)\n",
      "                elif current_section is None:\n",
      "                    raise LASError(\"Missing first section.\")\n",
      "                else:\n",
      "                    if other:\n",
      "                        # The 'Other' section is just lines of text, so we\n",
      "                        # assemble them into a single string.\n",
      "                        self.other += line\n",
      "                        current_section = self.other\n",
      "                    else:\n",
      "                        # Parse the line into a LASItem and add it to the\n",
      "                        # current section.\n",
      "                        m = LASItem.from_line(line)\n",
      "                        current_section.add_item(m)\n",
      "                        # Check for the required items whose values we'll\n",
      "                        # store as attributes of the LASReader instance.\n",
      "                        if current_section == self.version:\n",
      "                            if m.name == 'WRAP':\n",
      "                                if m.data.strip() == 'YES':\n",
      "                                    self.wrap = True\n",
      "                            if m.name == 'VERS':\n",
      "                                self.vers = m.data.strip()\n",
      "                        if current_section == self.well:\n",
      "                            if m.name == 'NULL':\n",
      "                                self.null = float(m.data)\n",
      "                            elif m.name == 'STRT':\n",
      "                                self.start = float(m.data)\n",
      "                                self.start_units = m.units\n",
      "                            elif m.name == 'STOP':\n",
      "                                self.stop = float(m.data)\n",
      "                                self.stop_units = m.units\n",
      "                            elif m.name == 'STEP':\n",
      "                                self.step = float(m.data)\n",
      "                                self.step_units = m.units\n",
      "            line = f.readline()\n",
      "\n",
      "        # Finished reading the header--all that is left is the numerical\n",
      "        # data that follows the '~A' line.  We'll construct a structured\n",
      "        # data type, and, if the data is not wrapped, use numpy.loadtext\n",
      "        # to read the data into an array.  For wrapped rows, we use the\n",
      "        # function _read_wrapped() defined elsewhere in this module.\n",
      "        # The data type is determined by the items from the '~Curves' section.\n",
      "        dt = np.dtype([(name, float) for name in self.curves.names])\n",
      "        if self.wrap:\n",
      "            a = _read_wrapped_data(f, dt)\n",
      "        else:\n",
      "            a = np.loadtxt(f, dtype=dt)\n",
      "        self.data = a\n",
      "\n",
      "        if opened_here:\n",
      "            f.close()\n",
      "\n",
      "\n",
      "if __name__ == \"__main__\":\n",
      "    import sys\n",
      "\n",
      "    las = LASReader(sys.argv[1], null_subs=np.nan)\n",
      "    print \"wrap? \", las.wrap\n",
      "    print \"vers? \", las.vers\n",
      "    print \"null =\", las.null\n",
      "    print \"start =\", las.start\n",
      "    print \"stop  =\", las.stop\n",
      "    print \"step  =\", las.step\n",
      "    print \"Version ---\"\n",
      "    las.version.display()\n",
      "    print \"Well ---\"\n",
      "    las.well.display()\n",
      "    print \"Curves ---\"\n",
      "    las.curves.display()\n",
      "    print \"Parameters ---\"\n",
      "    las.parameters.display()\n",
      "    print \"Other ---\"\n",
      "    print las.other\n",
      "    print \"Data ---\"\n",
      "    print las.data2d"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "Source code: [las.py](files/attachments/LASReader/las.py)\n",
      "\n",
      "Here's an example of the use of this module:"
     ]
    },
    {
     "cell_type": "code",
     "collapsed": false,
     "input": [
      ">>> import numpy as np\n",
      ">>> from las import LASReader\n",
      ">>> sample3 = LASReader('sample3.las', null_subs=np.nan)\n",
      ">>> print sample3.null\n",
      "-999.25\n",
      ">>> print sample3.start, sample3.stop, sample3.step\n",
      "910.0 909.5 -0.125\n",
      ">>> print sample3.well.PROV.data, sample3.well.UWI.data\n",
      "ALBERTA 100123401234W500\n",
      ">>> from matplotlib.pyplot import plot, show\n",
      ">>> plot(sample3.data['DEPT'], sample3.data['PHIE'])\n",
      "[<matplotlib.lines.Line2D object at 0x4c2ae90>]\n",
      ">>> show()"
     ],
     "language": "python",
     "metadata": {},
     "outputs": []
    },
    {
     "cell_type": "markdown",
     "metadata": {},
     "source": [
      "It creates the following plot:\n",
      "\n",
      "[sample3plot.png](files/attachments/LASReader/sample3plot.png)\n",
      "\n",
      "The sample LAS file is here:\n",
      "\n",
      "[sample3.las](files/attachments/LASReader/sample3.las)\n"
     ]
    }
   ],
   "metadata": {}
  }
 ]
}